/*=====================================================================

PIXHAWK Micro Air Vehicle Flying Robotics Toolkit

(c) 2009 PIXHAWK PROJECT  <http://pixhawk.ethz.ch>

This file is part of the PIXHAWK project

    PIXHAWK is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    PIXHAWK is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with PIXHAWK. If not, see <http://www.gnu.org/licenses/>.

======================================================================*/

/**
 * @file
 *   @brief Helper functions
 *
 *   @author Lorenz Meier <mavteam@student.ethz.ch>
 *
 */


#ifndef _MG_H_
#define _MG_H_

#include <QDateTime>
#include <QDebug>
#include <QDir>
#include <QThread>
#include <cmath>

namespace MG
{
    const static int MAX_FLIGHT_TIME = 60 * 60 * 24 * 21;

    class VERSION
    {
    public:

        static const int MAJOR = 1;
        static const int MINOR = 01;
    };

    class SYSTEM
    {
    public:

        static const int ID = 127;
        static const int COMPID = 0;
        static int getID()
        {
            return SYSTEM::ID;
        }
    };


    class SLEEP : public QThread
    {
    public:
        /**
         * @brief Set a thread to sleep for seconds
         * @param s time in seconds to sleep
         **/
        static void sleep(unsigned long s)
        {
            QThread::sleep(s);
        }
        /**
         * @brief Set a thread to sleep for milliseconds
         * @param ms time in milliseconds to sleep
         **/
        static void msleep(unsigned long ms)
        {
            QThread::msleep(ms);
        }
        /**
         * @brief Set a thread to sleep for microseconds
         * @param us time in microseconds to sleep
         **/
        static void usleep(unsigned long us)
        {
            QThread::usleep(us);
        }
    };


    class UNITS
    {
    public:

        /** The distance units supported for display by the groundstation **/
        enum DistanceUnit {
            METER,
            CENTIMETER,
            MILLIMETER,
            INCH,
            FEET,
            MILE
        };

        /**
         * @brief Convert a distance in meters into another distance unit system
         *
         * @param in The distance to convert
         * @param newUnit The new unit to convert to
         *
         * @return The converted distance
         */
        static double convertFromMeter(double in, DistanceUnit newUnit)
        {
            double result = in;

            // Conversion table in meter
            static const double inInch = 39.3700787;
            static const double inFeet = 3.2808399;
            static const double inMile = 0.000000621371192;
            static const double inCentimeter = 100;
            static const double inMillimeter = 1000;

            switch (newUnit) {
            case METER:
                result = in;
                break;
            case CENTIMETER:
                result = in * inCentimeter;
                break;
            case MILLIMETER:
                result = in * inMillimeter;
                break;
            case INCH:
                result = in * inInch;
                break;
            case FEET:
                result = in * inFeet;
                break;
            case MILE:
                result = in * inMile;
                break;
            }

            return result;
        }

        /**
         * @brief Convert between two distance units
         *
         * This convenience function allows to convert between arbitrary distance units
         *
         * @param in The input distance
         * @param inputUnit The input unit
         * @param outputUnit The output unit
         *
         * @return The converted distance
         */
        static double convert(double in, DistanceUnit inputUnit, DistanceUnit outputUnit)
        {
            double meters = convertToMeter(in, inputUnit);
            return convertFromMeter(meters, outputUnit);
        }

        /**
         * @brief Convert a distance to the meter unit
         *
         * @param in The distance to convert
         * @param inputUnit The unit the distance is represented in
         *
         * @return The converted distance
         */
        static double convertToMeter(double in, DistanceUnit inputUnit) {

            double result = in;


            // Conversion table in meter
            static const double inInch = 39.3700787;
            static const double inFeet = 3.2808399;
            static const double inMile = 0.000000621371192;
            static const double inCentimeter = 100;
            static const double inMillimeter = 1000;

            // Don't convert if new unit is same unit

            switch (inputUnit) {
            case METER:
                result = in;
                break;
            case CENTIMETER:
                result = in / inCentimeter;
                break;
            case MILLIMETER:
                result = in / inMillimeter;
                break;
            case INCH:
                result = in / inInch;
                break;
            case FEET:
                result = in / inFeet;
                break;
            case MILE:
                result = in / inMile;
                break;
            }

            return result;
        }

    };

    class DISPLAY {

    public:

        DISPLAY() {
            // Initialize static class display with notebook display default value
            //pixelSize = 0.224f;
            //setPixelSize(0.224f);
        }

        ~DISPLAY() {

        }

        /**
         * @brief Get the size of a single pixel
         *
         * This value can be used to generate user interfaces with
         * a size in physical units, for example a gauge which is
         * always 50.8 mm (2") in diameter, regardless of the screen.
         *
         * @return The horizontal and vertical size of a pixel-square
         */
        static double getPixelSize() {
            return 0.224f;
        }

        /**
         * @brief Set the size of a single pixel
         *
         * @param size The horizontal and vertical size of a pixel-square
         */
        static void setPixelSize(double size) {
            pixelSize = size;
        }

        /**
         * @brief Set the size of a single pixel
         *
         * This method calculates the pixel size from the vertical and horizontal
         * resolution and the screen diameter. The diameter is in mm (as this unit
         * is a SI unit). One inch = 25.4 mm
         *
         * @param horizontalResolution The horizontal screen resolution, e.g. 1280.
         * @param verticalResolution The vertical screen resolution, e.g. 800.
         * @param screenDiameter The screen diameter in mm, e.g. 13.3" = 338 mm.
         */
        static void setPixelSize(int horizontalResolution, int verticalResolution, double screenDiameter) {
            pixelSize = screenDiameter / sqrt(static_cast<float>(horizontalResolution*horizontalResolution + verticalResolution*verticalResolution));
        }

    private:
        /** The size of a single pixel **/
        static double pixelSize;
    };

    class STAT {
        /** The time interval for the last few moments in milliseconds **/
        static const int SHORT_TERM_INTERVAL = 300;
        /** The time interval for the last moment in milliseconds **/
        static const int CURRENT_INTERVAL = 50;
    };

    class TIME {

    public:

        //static const QString ICONDIR = "./icons";

        /**
         * @brief Convenience method to get the milliseconds time stamp for now
         *
         * The timestamp is created at the instant of calling this method. It is
         * defined as the number of milliseconds since unix epoch, which is
         * 1.1.1970, 00:00 UTC.
         *
         * @return The number of milliseconds elapsed since unix epoch
         * @deprecated Will the replaced by time helper class
         **/
        static quint64 getGroundTimeNow() {
            QDateTime time = QDateTime::currentDateTime();
            time = time.toUTC();
            /* Return seconds and milliseconds, in milliseconds unit */
            quint64 milliseconds = time.toTime_t() * static_cast<quint64>(1000);
            return static_cast<quint64>(milliseconds + time.time().msec());
        }

        /**
         * @brief Convenience method to get the milliseconds time stamp for now
         *
         * The timestamp is created at the instant of calling this method. It is
         * defined as the number of milliseconds since unix epoch, which is
         * 1.1.1970, 00:00 UTC.
         *
         * @return The number of milliseconds elapsed since unix epoch
         * @deprecated Will the replaced by time helper class
         **/
        static quint64 getGroundTimeNowUsecs() {
            QDateTime time = QDateTime::currentDateTime();
            time = time.toUTC();
            /* Return seconds and milliseconds, in milliseconds unit */
            quint64 microseconds = time.toTime_t() * static_cast<quint64>(1000000);
            return static_cast<quint64>(microseconds + (time.time().msec()*1000));
        }

        /*tatic quint64 getMissionTimeUsecs()
        {
            ;
        }*/

        /**
         * Convert milliseconds to an QDateTime object. This method converts the amount of
         * milliseconds since 1.1.1970, 00:00 UTC (unix epoch) to a QDateTime date object.
         *
         * @param msecs The milliseconds since unix epoch (in Qt unsigned 64bit integer type quint64)
         * @return The QDateTime object set to corresponding date and time
         * @deprecated Will the replaced by time helper class
         **/
        static QDateTime msecToQDateTime(quint64 msecs) {
            QDateTime time = QDateTime();
            /* Set date and time depending on the seconds since unix epoch,
                 * integer division truncates the milliseconds */
            time.setTime_t(msecs / 1000);
            /* Add the milliseconds, modulo returns the milliseconds part */
            return time.addMSecs(msecs % 1000);
        }

    };

    class DIR {

    public:
        /**
         * @brief Get the current support file directory.
         *
         * The support files are files like icons or fonts and are typically found in the
         * same directory as the main executable.
         *
         * @return The absolute path of the directory
         **/
        static QString getSupportFilesDirectory() {

            // Checks if the software is executed in the development environment
            QString path = QDir::current().absolutePath();
            QDir currentDir = QDir::current();

            if (QDir::current().dirName().toLower() == "debug")
            {
                // Debug folder of development environment
                path.append("/..");
            }
            else if (QDir::current().dirName().toLower() == "release")
            {
                // Release folder of development environment
                path.append("/..");
            }
            else if (QDir::current().dirName().toLower() == "bin")
            {
                // Release folder of development environment
                path.append("/..");
            }
            else if (QDir::current().dirName().toLower() == "macos")
            {
                // Mac application bundle in development environment
                path.append("/../../../../..");
            }

            // Check if we are still in a development folder
            if(currentDir.cdUp())
            {
                if(currentDir.dirName().toLower() == "build")
                {
                    path.append("/..");
                }
            }
            //TODO The Mac application bundle in distribution is not yet included here
            //qDebug() << "MG::supportfilesdirectory" << path;
            return path;
        }

        /**
         * @brief Get the current icon directory.
         *
         * The icon directory is typically a subdirectory of the main directory,
         * but depends on the platform. For example in OS X it is part of the bundle.
         *
         * @return The absolute path of the icon directory
         **/
        static QString getIconDirectory() {
            return MG::DIR::getSupportFilesDirectory() + "/images/";
        }

    };

}

#endif // _MG_H_
